/**
 * 
 */
package org.windu2b.jcaddie;

import java.awt.*;
import java.beans.*;
import java.util.*;
import java.util.List;

import javax.swing.*;

import org.windu2b.jcaddie.io.*;
import org.windu2b.jcaddie.model.*;
import org.windu2b.jcaddie.swing.*;

/**
 * @author windu.2b
 * 
 */
public class Main extends CaddieApplication
{
	private CaddieRecorder	    caddieRecorder;


	private UserPreferences	    preferences;


	private Map<Caddie, JFrame>	caddieFrames;




	private Main()
	{
		caddieRecorder = new CaddieFileRecorder();
		preferences = new FileUserPreferences();
		caddieFrames = new HashMap<Caddie, JFrame>();
	}




	/**
	 * Returns a recorder able to write and read caddies in files.
	 * 
	 * @see org.windu2b.jcaddie.model.CaddieApplication#getCaddieRecorder()
	 */
	@Override
	public CaddieRecorder getCaddieRecorder()
	{
		return caddieRecorder;
	}




	/**
	 * Returns user preferences stored in resources and local file system.
	 * 
	 * @see org.windu2b.jcaddie.model.CaddieApplication#getUserPreferences()
	 */
	@Override
	public UserPreferences getUserPreferences()
	{
		return preferences;
	}




	/**
	 * Adds a given <code>caddie</code> to this application, in the Event
	 * Dispatch Thread.
	 */
	@Override
	public void addCaddie( final Caddie caddie )
	{
		EventQueue.invokeLater( new Runnable()
		{
			public void run()
			{
				Main.super.addCaddie( caddie );
			}
		} );
	}




	/**
	 * Returns the frame that displays a given <code>caddie</code>.
	 */
	public JFrame getCaddieFrame( Caddie caddie )
	{
		return this.caddieFrames.get( caddie );
	}



	// Only one application may be created with main method
	private static Main	application;




	/**
	 * JCaddie entry point.
	 * 
	 * @param args
	 *            may contain one .xjc file to open, following a
	 *            <code>-open</code> option.
	 */
	public static void maind( String[] args )
	{
		// At first main call
		if ( application == null )
		{
			initLookAndFeel();
			application = createApplication();
		}
		if ( args.length == 2 && args[0].equals( "-open" ) )
		{
			try
			{
				// Read caddie file in args [1] if args [0] == "-open"
				Caddie caddie = application.getCaddieRecorder().readCaddie(
				        args[1] );
				caddie.setName( args[1] );
				application.addCaddie( caddie );
			}
			catch ( RecorderException ex )
			{
				// Show an error message dialog if caddie couldn't be read
				ResourceBundle resource = ResourceBundle
				        .getBundle( CaddieController.class.getName() );
				String message = String.format( resource
				        .getString( "openError" ), args[1] );
				JOptionPane.showMessageDialog( null, message,
				        "Sweet Caddie 3D", JOptionPane.ERROR_MESSAGE );
			}
		}
		else if ( application.getCaddies().isEmpty() )
		{
			// Create a default caddie
			Caddie caddie = new Caddie();
			application.addCaddie( caddie );
		}
		else
		{
			// If no Sweet Caddie 3D frame has focus, bring last created viewed
			// frame to front
			final List<Caddie> caddies = application.getCaddies();
			JFrame frame = null;
			for ( int i = caddies.size() - 1; i >= 0; i-- )
			{
				JFrame caddieFrame = application.getCaddieFrame( caddies
				        .get( i ) );
				if ( caddieFrame.isActive()
				        || caddieFrame.getState() != JFrame.ICONIFIED )
				{
					frame = caddieFrame;
					break;
				}
			}
			// If no frame is visible and not iconified, take any displayable
			// frame
			if ( frame == null )
			{
				for ( int i = caddies.size() - 1; i >= 0; i-- )
				{
					JFrame caddieFrame = application.getCaddieFrame( caddies
					        .get( i ) );
					if ( caddieFrame.isDisplayable() )
					{
						frame = caddieFrame;
						break;
					}
				}
			}

			final JFrame shownFrame = frame;
			EventQueue.invokeLater( new Runnable()
			{
				public void run()
				{
					shownFrame.setVisible( true );
					shownFrame.setState( JFrame.NORMAL );
					shownFrame.toFront();
				}
			} );
		}
	}




	public static void main( String[] args )
	{
		UserPreferences preferences = new DefaultUserPreferences();
		Caddie caddie = new Caddie();

		new MainController( caddie, preferences );
	}



	private static class MainController extends CaddieController
	{
		public MainController( Caddie caddie, UserPreferences preferences )
		{
			super( caddie, preferences );
			new ViewTest( this ).displayView();
		}
	}

	private static class ViewTest extends JRootPane
	{
		/**
		 * 
		 */
		private static final long	serialVersionUID	= 542545669141595334L;




		public ViewTest( final CaddieController controller )
		{
			getContentPane().add( controller.getView() );
		}




		public void displayView()
		{
			JFrame frame = new JFrame( "Caddie Controller Test" )
			{
				/**
				 * 
				 */
				private static final long	serialVersionUID	= -3429466836624869291L;

				{
					setRootPane( ViewTest.this );
				}
			};
			frame.setPreferredSize( new Dimension( 640, 480 ) );
			frame.pack();
			frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
			frame.setVisible( true );
		}
	}




	/**
	 * Sets application look anf feel and various <code>System</code>
	 * properties.
	 */
	private static void initLookAndFeel()
	{
		// Enables Java 5 bug correction about dragging directly
		// a tree element without selecting it before :
		// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4521075
		System.setProperty( "sun.swing.enableImprovedDragGesture", "true" );
		// Change Mac OS X application menu name
		System.setProperty( "com.apple.mrj.application.apple.menu.about.name",
		        "Sweet Caddie 3D" );
		// Use Mac OS X screen menu bar for frames menu bar
		System.setProperty( "apple.laf.useScreenMenuBar", "true" );
		try
		{
			// Apply current system look and feel
			UIManager
			        .setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
		}
		catch ( Exception e )
		{
			// Too bad keep current look and feel
		}
	}




	/**
	 * Returns main application object.
	 */
	private static Main createApplication()
	{
		// Create the application that manages caddies
		final Main application = new Main();
		// Add a listener that opens a frame when a caddie is added to
		// application
		application.addCaddieListener( new CaddieListener()
		{
			private boolean	firstApplicationCaddieAdded;




			public void caddieChanged( CaddieEvent ev )
			{
				switch ( ev.getType() )
				{
					case ADD :
						Caddie caddie = ev.getCaddie();
						CaddieController controller = new CaddieFrameController(
						        caddie, application );
						if ( !this.firstApplicationCaddieAdded )
						{
							application.addNewCaddieCloseListener( caddie,
							        controller );
							this.firstApplicationCaddieAdded = true;
						}

						JFrame caddieFrame = ( JFrame ) SwingUtilities
						        .getRoot( controller.getView() );
						application.caddieFrames.put( caddie, caddieFrame );
						break;
					case DELETE :
						application.caddieFrames.remove( ev.getCaddie() );

						// If application has no more caddie
						if ( application.getCaddies().isEmpty()
						        && !System.getProperty( "os.name" ).startsWith(
						                "Mac OS X" ) )
						{
							// Exit (under Mac OS X, exit is managed by
							// MacOSXConfiguration)
							System.exit( 0 );
						}
						break;
				}
			};
		} );

		/*if ( System.getProperty( "os.name" ).startsWith( "Mac OS X" ) )
		{
			// Bind to application menu
			MacOSXConfiguration.bindToApplicationMenu( application );
		}*/

		return application;
	}




	/**
	 * Adds a listener to new caddie to close it if an other one is opened.
	 */
	private void addNewCaddieCloseListener( final Caddie caddie,
	        final CaddieController controller )
	{
		if ( caddie.getName() == null )
		{
			final CaddieListener newCaddieListener = new CaddieListener()
			{
				public void caddieChanged( CaddieEvent ev )
				{
					// Close new caddie for any named caddie added to
					// application
					if ( ev.getType() == CaddieEvent.Type.ADD )
					{
						if ( ev.getCaddie().getName() != null
						        && caddie.getName() == null )
						{
							controller.close();
						}
						removeCaddieListener( this );
					}
					else if ( ev.getCaddie() == caddie
					        && ev.getType() == CaddieEvent.Type.DELETE )
					{
						removeCaddieListener( this );
					}
				}
			};
			addCaddieListener( newCaddieListener );
			// Disable this listener at first caddie change
			caddie.addPropertyChangeListener( Caddie.Property.MODIFIED,
			        new PropertyChangeListener()
			        {
				        public void propertyChange( PropertyChangeEvent ev )
				        {
					        removeCaddieListener( newCaddieListener );
					        caddie.removePropertyChangeListener(
					                Caddie.Property.MODIFIED, this );
				        }
			        } );
		}
	}
}